मेमोरी उपयोग और प्रदर्शन के लिए पांडास डेटाफ्रेम को ऑप्टिमाइज़ करने की एक व्यापक गाइड, जिसमें डेटा प्रकार, इंडेक्सिंग और उन्नत तकनीकें शामिल हैं।
पांडास डेटाफ्रेम ऑप्टिमाइज़ेशन: मेमोरी उपयोग और प्रदर्शन ट्यूनिंग
पांडास डेटा मैनिपुलेशन और विश्लेषण के लिए एक शक्तिशाली पायथन लाइब्रेरी है। हालांकि, बड़े डेटासेट के साथ काम करते समय, पांडास डेटाफ्रेम काफी मेमोरी की खपत कर सकते हैं और धीमा प्रदर्शन दिखा सकते हैं। यह लेख मेमोरी उपयोग और प्रदर्शन दोनों के लिए पांडास डेटाफ्रेम को ऑप्टिमाइज़ करने के लिए एक व्यापक गाइड प्रदान करता है, जिससे आप बड़े डेटासेट को अधिक कुशलता से प्रोसेस कर सकते हैं।
पांडास डेटाफ्रेम में मेमोरी उपयोग को समझना
ऑप्टिमाइज़ेशन तकनीकों में जाने से पहले, यह समझना महत्वपूर्ण है कि पांडास डेटाफ्रेम मेमोरी में डेटा कैसे संग्रहीत करते हैं। डेटाफ्रेम में प्रत्येक कॉलम का एक विशिष्ट डेटा प्रकार होता है, जो उसके मानों को संग्रहीत करने के लिए आवश्यक मेमोरी की मात्रा निर्धारित करता है। सामान्य डेटा प्रकारों में शामिल हैं:
- int64: 64-बिट पूर्णांक (पूर्णांकों के लिए डिफ़ॉल्ट)
- float64: 64-बिट फ्लोटिंग-पॉइंट संख्याएँ (फ्लोटिंग-पॉइंट संख्याओं के लिए डिफ़ॉल्ट)
- object: पायथन ऑब्जेक्ट्स (स्ट्रिंग्स और मिश्रित डेटा प्रकारों के लिए उपयोग किया जाता है)
- category: कैटेगोरिकल डेटा (दोहराए जाने वाले मानों के लिए कुशल)
- bool: बूलियन मान (True/False)
- datetime64: डेटाइम मान
object डेटा प्रकार अक्सर सबसे अधिक मेमोरी-गहन होता है क्योंकि यह पायथन ऑब्जेक्ट्स के पॉइंटर्स को संग्रहीत करता है, जो पूर्णांक या फ्लोट जैसे प्रिमिटिव डेटा प्रकारों से काफी बड़े हो सकते हैं। स्ट्रिंग्स, भले ही छोटे हों, जब `object` के रूप में संग्रहीत होते हैं, तो वे आवश्यकता से कहीं अधिक मेमोरी की खपत करते हैं। इसी तरह, जब `int32` पर्याप्त हो तो `int64` का उपयोग करना मेमोरी की बर्बादी है।
उदाहरण: डेटाफ्रेम मेमोरी उपयोग का निरीक्षण करना
आप डेटाफ्रेम के मेमोरी उपयोग का निरीक्षण करने के लिए memory_usage() विधि का उपयोग कर सकते हैं:
import pandas as pd
import numpy as np
data = {
'col1': np.random.randint(0, 1000, 100000),
'col2': np.random.rand(100000),
'col3': ['A', 'B', 'C'] * (100000 // 3 + 1)[:100000],
'col4': ['This is a long string'] * 100000
}
df = pd.DataFrame(data)
memory_usage = df.memory_usage(deep=True)
print(memory_usage)
print(df.dtypes)
deep=True आर्गुमेंट यह सुनिश्चित करता है कि ऑब्जेक्ट्स (जैसे स्ट्रिंग्स) के मेमोरी उपयोग की सटीक गणना की जाती है। `deep=True` के बिना, यह केवल पॉइंटर्स के लिए मेमोरी की गणना करेगा, अंतर्निहित डेटा की नहीं।
डेटा प्रकारों का ऑप्टिमाइज़ेशन
मेमोरी उपयोग को कम करने के सबसे प्रभावी तरीकों में से एक है अपने डेटाफ्रेम कॉलम के लिए सबसे उपयुक्त डेटा प्रकार चुनना। यहाँ कुछ सामान्य तकनीकें हैं:
1. संख्यात्मक डेटा प्रकारों को डाउनकास्ट करना
यदि आपके पूर्णांक या फ्लोटिंग-पॉइंट कॉलम को 64-बिट परिशुद्धता की पूरी श्रृंखला की आवश्यकता नहीं है, तो आप उन्हें छोटे डेटा प्रकारों जैसे int32, int16, float32, या float16 में डाउनकास्ट कर सकते हैं। यह विशेष रूप से बड़े डेटासेट के लिए मेमोरी उपयोग को काफी कम कर सकता है।
उदाहरण: उम्र का प्रतिनिधित्व करने वाले एक कॉलम पर विचार करें, जिसके 120 से अधिक होने की संभावना नहीं है। इसे `int64` के रूप में संग्रहीत करना व्यर्थ है; `int8` (रेंज -128 से 127) अधिक उपयुक्त होगा।
def downcast_numeric(df):
"""Downcasts numeric columns to the smallest possible data type."""
for col in df.columns:
if pd.api.types.is_integer_dtype(df[col]):
df[col] = pd.to_numeric(df[col], downcast='integer')
elif pd.api.types.is_float_dtype(df[col]):
df[col] = pd.to_numeric(df[col], downcast='float')
return df
df = downcast_numeric(df.copy())
print(df.memory_usage(deep=True))
print(df.dtypes)
pd.to_numeric() फ़ंक्शन downcast आर्गुमेंट के साथ स्वचालित रूप से सबसे छोटे संभव डेटा प्रकार का चयन करने के लिए उपयोग किया जाता है जो कॉलम में मानों का प्रतिनिधित्व कर सकता है। `copy()` मूल डेटाफ्रेम को संशोधित करने से बचाता है। जानकारी खोने से बचने के लिए डाउनकास्टिंग से पहले हमेशा अपने डेटा में मानों की सीमा की जांच करें।
2. कैटेगोरिकल डेटा प्रकारों का उपयोग करना
यदि किसी कॉलम में सीमित संख्या में अद्वितीय मान हैं, तो आप इसे category डेटा प्रकार में बदल सकते हैं। कैटेगोरिकल डेटा प्रकार प्रत्येक अद्वितीय मान को केवल एक बार संग्रहीत करते हैं, और फिर कॉलम में मानों का प्रतिनिधित्व करने के लिए पूर्णांक कोड का उपयोग करते हैं। यह मेमोरी उपयोग को काफी कम कर सकता है, विशेष रूप से उन कॉलमों के लिए जिनमें दोहराए गए मानों का उच्च अनुपात होता है।
उदाहरण: देश के कोड का प्रतिनिधित्व करने वाले एक कॉलम पर विचार करें। यदि आप देशों के एक सीमित सेट (जैसे, केवल यूरोपीय संघ के देश) के साथ काम कर रहे हैं, तो इसे एक श्रेणी के रूप में संग्रहीत करना इसे स्ट्रिंग्स के रूप में संग्रहीत करने की तुलना में बहुत अधिक कुशल होगा।
def optimize_categories(df):
"""Converts object columns with low cardinality to categorical type."""
for col in df.columns:
if df[col].dtype == 'object':
num_unique_values = len(df[col].unique())
num_total_values = len(df[col])
if num_unique_values / num_total_values < 0.5:
df[col] = df[col].astype('category')
return df
df = optimize_categories(df.copy())
print(df.memory_usage(deep=True))
print(df.dtypes)
यह कोड जाँचता है कि क्या किसी ऑब्जेक्ट कॉलम में अद्वितीय मानों की संख्या कुल मानों के 50% से कम है। यदि ऐसा है, तो यह कॉलम को कैटेगोरिकल डेटा प्रकार में परिवर्तित कर देता है। 50% की सीमा मनमानी है और इसे आपके डेटा की विशिष्ट विशेषताओं के आधार पर समायोजित किया जा सकता है। यह दृष्टिकोण तब सबसे अधिक फायदेमंद होता है जब कॉलम में कई दोहराए गए मान होते हैं।
3. स्ट्रिंग्स के लिए ऑब्जेक्ट डेटा प्रकारों से बचना
जैसा कि पहले उल्लेख किया गया है, object डेटा प्रकार अक्सर सबसे अधिक मेमोरी-गहन होता है, खासकर जब स्ट्रिंग्स को संग्रहीत करने के लिए उपयोग किया जाता है। यदि संभव हो, तो स्ट्रिंग कॉलम के लिए object डेटा प्रकारों का उपयोग करने से बचने का प्रयास करें। कम कार्डिनैलिटी वाले स्ट्रिंग्स के लिए कैटेगोरिकल प्रकार बेहतर होते हैं। यदि कार्डिनैलिटी अधिक है, तो विचार करें कि क्या स्ट्रिंग्स को संख्यात्मक कोड के साथ दर्शाया जा सकता है या क्या स्ट्रिंग डेटा से पूरी तरह बचा जा सकता है।
यदि आपको कॉलम पर स्ट्रिंग ऑपरेशन करने की आवश्यकता है, तो आपको इसे ऑब्जेक्ट प्रकार के रूप में रखने की आवश्यकता हो सकती है, लेकिन विचार करें कि क्या ये ऑपरेशन पहले किए जा सकते हैं, फिर अधिक कुशल प्रकार में परिवर्तित किए जा सकते हैं।
4. दिनांक और समय डेटा
दिनांक और समय की जानकारी के लिए `datetime64` डेटा प्रकार का उपयोग करें। सुनिश्चित करें कि रिज़ॉल्यूशन उपयुक्त है (नैनोसेकंड रिज़ॉल्यूशन अनावश्यक हो सकता है)। पांडास टाइम सीरीज़ डेटा को बहुत कुशलता से संभालता है।
डेटाफ्रेम ऑपरेशंस को ऑप्टिमाइज़ करना
डेटा प्रकारों को ऑप्टिमाइज़ करने के अलावा, आप पांडास डेटाफ्रेम पर किए जाने वाले ऑपरेशंस को ऑप्टिमाइज़ करके भी प्रदर्शन में सुधार कर सकते हैं। यहाँ कुछ सामान्य तकनीकें हैं:
1. वेक्टराइज़ेशन
वेक्टराइज़ेशन व्यक्तिगत तत्वों पर पुनरावृति करने के बजाय, एक साथ पूरे ऐरे या कॉलम पर ऑपरेशन करने की प्रक्रिया है। पांडास वेक्टराइज़्ड ऑपरेशंस के लिए अत्यधिक ऑप्टिमाइज़ किया गया है, इसलिए उनका उपयोग करने से प्रदर्शन में काफी सुधार हो सकता है। जब भी संभव हो, स्पष्ट लूप से बचें। पांडास के अंतर्निहित फ़ंक्शन आमतौर पर समकक्ष पायथन लूप की तुलना में बहुत तेज़ होते हैं।
उदाहरण: प्रत्येक मान के वर्ग की गणना करने के लिए एक कॉलम के माध्यम से पुनरावृति करने के बजाय, pow() फ़ंक्शन का उपयोग करें:
# Inefficient (using a loop)
import time
start_time = time.time()
results = []
for value in df['col2']:
results.append(value ** 2)
df['col2_squared_loop'] = results
end_time = time.time()
print(f"Loop time: {end_time - start_time:.4f} seconds")
# Efficient (using vectorization)
start_time = time.time()
df['col2_squared_vectorized'] = df['col2'] ** 2
end_time = time.time()
print(f"Vectorized time: {end_time - start_time:.4f} seconds")
वेक्टराइज़्ड दृष्टिकोण आमतौर पर लूप-आधारित दृष्टिकोण की तुलना में कई गुना तेज़ होता है।
2. apply() का सावधानी से उपयोग करना
apply() विधि आपको डेटाफ्रेम की प्रत्येक पंक्ति या कॉलम में एक फ़ंक्शन लागू करने की अनुमति देती है। हालांकि, यह आम तौर पर वेक्टराइज़्ड ऑपरेशंस की तुलना में धीमा होता है क्योंकि इसमें प्रत्येक तत्व के लिए एक पायथन फ़ंक्शन को कॉल करना शामिल होता है। apply() का उपयोग केवल तभी करें जब वेक्टराइज़्ड ऑपरेशंस संभव न हों।
यदि आपको `apply()` का उपयोग करना ही है, तो आप जिस फ़ंक्शन को लागू कर रहे हैं, उसे जितना संभव हो सके वेक्टराइज़ करने का प्रयास करें। महत्वपूर्ण प्रदर्शन सुधार के लिए फ़ंक्शन को मशीन कोड में संकलित करने के लिए नंबा के `jit` डेकोरेटर का उपयोग करने पर विचार करें।
from numba import jit
@jit(nopython=True)
def my_function(x):
return x * 2 # Example function
df['col2_applied'] = df['col2'].apply(my_function)
3. कॉलम का कुशलतापूर्वक चयन करना
डेटाफ्रेम से कॉलम के सबसेट का चयन करते समय, इष्टतम प्रदर्शन के लिए निम्नलिखित विधियों का उपयोग करें:
- प्रत्यक्ष कॉलम चयन:
df[['col1', 'col2']](कुछ कॉलम चुनने के लिए सबसे तेज़) - बूलियन इंडेक्सिंग:
df.loc[:, [True if col.startswith('col') else False for col in df.columns]](एक शर्त के आधार पर कॉलम चुनने के लिए उपयोगी)
कॉलम चुनने के लिए रेगुलर एक्सप्रेशंस के साथ df.filter() का उपयोग करने से बचें, क्योंकि यह अन्य तरीकों की तुलना में धीमा हो सकता है।
4. जॉइन्स और मर्जेज को ऑप्टिमाइज़ करना
डेटाफ्रेम को जॉइन और मर्ज करना कम्प्यूटेशनल रूप से महंगा हो सकता है, खासकर बड़े डेटासेट के लिए। जॉइन्स और मर्जेज को ऑप्टिमाइज़ करने के लिए यहां कुछ सुझाव दिए गए हैं:
- उपयुक्त जॉइन कीज़ का उपयोग करें: सुनिश्चित करें कि जॉइन कीज़ का डेटा प्रकार समान है और वे अनुक्रमित हैं।
- जॉइन प्रकार निर्दिष्ट करें: अपनी आवश्यकताओं के आधार पर उपयुक्त जॉइन प्रकार (जैसे,
inner,left,right,outer) का उपयोग करें। एक इनर जॉइन आम तौर पर एक आउटर जॉइन की तुलना में तेज़ होता है। join()के बजायmerge()का उपयोग करें:merge()फ़ंक्शन अधिक बहुमुखी है और अक्सरjoin()विधि की तुलना में तेज़ होता है।
उदाहरण:
df1 = pd.DataFrame({'key': ['A', 'B', 'C', 'D'], 'value1': [1, 2, 3, 4]})
df2 = pd.DataFrame({'key': ['B', 'D', 'E', 'F'], 'value2': [5, 6, 7, 8]})
# Efficient inner join
df_merged = pd.merge(df1, df2, on='key', how='inner')
print(df_merged)
5. अनावश्यक रूप से डेटाफ्रेम कॉपी करने से बचना
कई पांडास ऑपरेशन डेटाफ्रेम की प्रतियां बनाते हैं, जो मेमोरी-गहन और समय लेने वाला हो सकता है। अनावश्यक कॉपी करने से बचने के लिए, उपलब्ध होने पर inplace=True आर्गुमेंट का उपयोग करें, या किसी ऑपरेशन के परिणाम को मूल डेटाफ्रेम में वापस असाइन करें। `inplace=True` के साथ बहुत सतर्क रहें क्योंकि यह त्रुटियों को छिपा सकता है और डिबगिंग को कठिन बना सकता है। पुनः असाइन करना अक्सर सुरक्षित होता है, भले ही थोड़ा कम प्रदर्शनकारी हो।
उदाहरण:
# Inefficient (creates a copy)
df_filtered = df[df['col1'] > 500]
# Efficient (modifies the original DataFrame in place - CAUTION)
df.drop(df[df['col1'] <= 500].index, inplace=True)
#SAFER - reassigns, avoids inplace
df = df[df['col1'] > 500]
6. चंकिंग और इटरेटिंग
अत्यंत बड़े डेटासेट के लिए जो मेमोरी में फिट नहीं हो सकते, डेटा को चंक्स (टुकड़ों) में संसाधित करने पर विचार करें। फ़ाइलों से डेटा पढ़ते समय `chunksize` पैरामीटर का उपयोग करें। चंक्स के माध्यम से पुनरावृति करें और प्रत्येक चंक पर अलग-अलग अपना विश्लेषण करें। इसके लिए यह सुनिश्चित करने के लिए सावधानीपूर्वक योजना बनाने की आवश्यकता है कि विश्लेषण सही रहे, क्योंकि कुछ ऑपरेशनों को एक बार में पूरे डेटासेट को संसाधित करने की आवश्यकता होती है।
# Read CSV in chunks
for chunk in pd.read_csv('large_data.csv', chunksize=100000):
# Process each chunk
print(chunk.shape)
7. समानांतर प्रसंस्करण के लिए डास्क (Dask) का उपयोग करना
डास्क एक समानांतर कंप्यूटिंग लाइब्रेरी है जो पांडास के साथ सहजता से एकीकृत होती है। यह आपको बड़े डेटाफ्रेम को समानांतर में संसाधित करने की अनुमति देता है, जिससे प्रदर्शन में काफी सुधार हो सकता है। डास्क डेटाफ्रेम को छोटे विभाजनों में विभाजित करता है और उन्हें कई कोर या मशीनों में वितरित करता है।
import dask.dataframe as dd
# Create a Dask DataFrame
ddf = dd.read_csv('large_data.csv')
# Perform operations on the Dask DataFrame
ddf_filtered = ddf[ddf['col1'] > 500]
# Compute the result (this triggers the parallel computation)
result = ddf_filtered.compute()
print(result.head())
तेज़ लुकअप के लिए इंडेक्सिंग
एक कॉलम पर एक इंडेक्स बनाने से लुकअप और फ़िल्टरिंग ऑपरेशंस में काफी तेज़ी आ सकती है। पांडास एक विशिष्ट मान से मेल खाने वाली पंक्तियों को जल्दी से खोजने के लिए इंडेक्स का उपयोग करता है।
उदाहरण:
# Set 'col3' as the index
df = df.set_index('col3')
# Faster lookup
value = df.loc['A']
print(value)
# Reset the index
df = df.reset_index()
हालांकि, बहुत सारे इंडेक्स बनाने से मेमोरी उपयोग बढ़ सकता है और लिखने के संचालन को धीमा कर सकता है। केवल उन कॉलम पर इंडेक्स बनाएं जिनका उपयोग अक्सर लुकअप या फ़िल्टरिंग के लिए किया जाता है।
अन्य विचार
- हार्डवेयर: यदि आप लगातार बड़े डेटासेट के साथ काम कर रहे हैं तो अपने हार्डवेयर (सीपीयू, रैम, एसएसडी) को अपग्रेड करने पर विचार करें।
- सॉफ्टवेयर: सुनिश्चित करें कि आप पांडास के नवीनतम संस्करण का उपयोग कर रहे हैं, क्योंकि नए संस्करणों में अक्सर प्रदर्शन सुधार शामिल होते हैं।
- प्रोफाइलिंग: अपने कोड में प्रदर्शन की बाधाओं की पहचान करने के लिए प्रोफाइलिंग टूल (जैसे,
cProfile,line_profiler) का उपयोग करें। - डेटा स्टोरेज फॉर्मेट: CSV के बजाय Parquet या Feather जैसे अधिक कुशल डेटा स्टोरेज फॉर्मेट का उपयोग करने पर विचार करें। ये फॉर्मेट कॉलमर होते हैं और अक्सर संपीड़ित होते हैं, जिससे फ़ाइल का आकार छोटा होता है और पढ़ने/लिखने का समय तेज़ होता है।
निष्कर्ष
बड़े डेटासेट के साथ कुशलता से काम करने के लिए मेमोरी उपयोग और प्रदर्शन के लिए पांडास डेटाफ्रेम को ऑप्टिमाइज़ करना महत्वपूर्ण है। उपयुक्त डेटा प्रकार चुनकर, वेक्टराइज़्ड ऑपरेशंस का उपयोग करके, और अपने डेटा को प्रभावी ढंग से अनुक्रमित करके, आप मेमोरी की खपत को काफी कम कर सकते हैं और प्रदर्शन में सुधार कर सकते हैं। प्रदर्शन की बाधाओं की पहचान करने के लिए अपने कोड को प्रोफाइल करना याद रखें और अत्यंत बड़े डेटासेट के लिए चंकिंग या डास्क का उपयोग करने पर विचार करें। इन तकनीकों को लागू करके, आप डेटा विश्लेषण और मैनिपुलेशन के लिए पांडास की पूरी क्षमता को अनलॉक कर सकते हैं।